# -*- coding: utf-8 -*-
"""
    xfree.utils.fields
    ~~~~~~~~~~~~~~~~~~~~~
    文件描述：这个模块包含了wtforms的额外字段和部件。其中的reCAPTCHA字段是从Flask - WTF获取并修改的，以使用自己的设置系统。
    作者：xfree
    邮箱：cy_83k@qq.com
"""

import json
import logging
import urllib
from urllib.parse import urlencode

from flask import current_app, request
from markupsafe import Markup
from wtforms import ValidationError
from wtforms.fields import Field

from xfree.utils.helpers import to_bytes, to_unicode
from xfree.utils.settings import xfree_config

# 获取日志记录器
logger = logging.getLogger(__name__)

JSONEncoder = json.JSONEncoder

# reCAPTCHA脚本的URL
RECAPTCHA_SCRIPT = "https://www.google.com/recaptcha/api.js"
# reCAPTCHA的模板字符串
RECAPTCHA_TEMPLATE = """
<script src='%s' async defer></script>
<div class="g-recaptcha" %s></div>
"""

# reCAPTCHA验证服务器的URL
RECAPTCHA_VERIFY_SERVER = "https://www.google.com/recaptcha/api/siteverify"
# reCAPTCHA错误代码对应的错误信息字典
RECAPTCHA_ERROR_CODES = {
    "missing - input - secret": "The secret parameter is missing.",
    "invalid - input - secret": "The secret parameter is invalid or malformed.",
    "missing - input - response": "The response parameter is missing.",
    "invalid - input - response": "The response parameter is invalid or malformed.",
}


class RecaptchaValidator(object):
    """
    用于验证ReCaptcha的类
    """

    def __init__(self, message=None):
        """
        初始化方法

        :param message: 错误消息，如果为None，则使用默认的错误消息（RECAPTCHA_ERROR_CODES["missing - input - response"]）
        """
        if message is None:
            message = RECAPTCHA_ERROR_CODES["missing - input - response"]
        self.message = message

    def __call__(self, form, field):
        """
        当这个验证器被调用时执行的方法

        :param form: 表单对象
        :param field: 字段对象
        """
        # 如果应用处于测试模式或者没有启用reCAPTCHA，则直接返回True，表示验证通过
        if current_app.testing or not xfree_config["RECAPTCHA_ENABLED"]:
            return True

        # 如果请求是JSON格式，从JSON数据中获取g - recaptcha - response的值，否则从表单数据中获取
        if request.json:
            response = request.json.get("g - recaptcha - response", "")
        else:
            response = request.form.get("g - recaptcha - response", "")
        # 获取远程IP地址
        remote_ip = request.remote_addr

        # 如果没有获取到响应，抛出验证错误
        if not response:
            raise ValidationError(field.gettext(self.message))

        # 进行reCAPTCHA验证，如果验证不通过，设置字段的recaptcha_error属性，并抛出验证错误
        if not self._validate_recaptcha(response, remote_ip):
            field.recaptcha_error = "incorrect - captcha - sol"
            raise ValidationError(field.gettext(self.message))

    def _validate_recaptcha(self, response, remote_ip):
        """
        执行实际的reCAPTCHA验证的方法

        :param response: 用户在前端提交的reCAPTCHA响应
        :param remote_ip: 用户的远程IP地址
        :return: 验证结果，True表示验证通过，False表示验证失败
        """
        try:
            # 获取reCAPTCHA私钥
            private_key = xfree_config["RECAPTCHA_PRIVATE_KEY"]
        except KeyError:
            # 如果没有设置私钥，抛出运行时错误
            raise RuntimeError("No RECAPTCHA_PRIVATE_KEY config set")

        # 对验证数据进行URL编码
        data = urlencode({"secret": private_key, "remoteip": remote_ip, "response": response})

        # 发送HTTP请求到reCAPTCHA验证服务器
        http_response = urllib.request.urlopen(RECAPTCHA_VERIFY_SERVER, to_bytes(data))

        # 如果HTTP响应码不是200，返回False，表示验证失败
        if http_response.code!= 200:
            return False

        # 解析服务器返回的JSON响应
        json_resp = json.loads(to_unicode(http_response.read()))

        # 如果响应中的success字段为True，表示验证通过，返回True
        if json_resp["success"]:
            return True

        # 如果有错误代码，遍历错误代码并抛出相应的验证错误
        for error in json_resp.get("error - codes", []):
            if error in RECAPTCHA_ERROR_CODES:
                raise ValidationError(RECAPTCHA_ERROR_CODES[error])

        return False


class RecaptchaWidget(object):
    def recaptcha_html(self, public_key):
        """
        生成reCAPTCHA HTML的方法

        :param public_key: reCAPTCHA公钥
        :return: 生成的reCAPTCHA HTML标记
        """
        html = current_app.config.get("RECAPTCHA_HTML")
        if html:
            return Markup(html)
        params = current_app.config.get("RECAPTCHA_PARAMETERS")
        script = RECAPTCHA_SCRIPT
        if params:
            script += "?" + urlencode(params)

        attrs = current_app.config.get("RECAPTCHA_DATA_ATTRS", {})
        attrs["sitekey"] = public_key
        snippet = " ".join(['data - %s="%s"' % (k, attrs[k]) for k in attrs])
        return Markup(RECAPTCHA_TEMPLATE % (script, snippet))

    def __call__(self, field, error=None, **kwargs):
        """
        返回reCAPTCHA输入HTML的方法

        :param field: 字段对象
        :param error: 错误信息（这里未使用）
        :param kwargs: 其他关键字参数（这里未使用）
        :return: reCAPTCHA输入HTML，如果没有启用reCAPTCHA，则返回None
        """
        if not xfree_config["RECAPTCHA_ENABLED"]:
            return

        try:
            public_key = xfree_config["RECAPTCHA_PUBLIC_KEY"]
        except KeyError:
            raise RuntimeError("RECAPTCHA_PUBLIC_KEY config not set")

        return self.recaptcha_html(public_key)


class RecaptchaField(Field):
    widget = RecaptchaWidget()

    # reCAPTCHA验证失败时的错误消息
    recaptcha_error = None

    def __init__(self, label="", validators=None, **kwargs):
        """
        初始化方法

        :param label: 字段标签（默认为空字符串）
        :param validators: 验证器列表（默认为包含RecaptchaValidator的列表）
        :param kwargs: 其他关键字参数
        """
        validators = validators or [RecaptchaValidator()]
        super(RecaptchaField, self).__init__(label, validators, **kwargs)